package cn.bird.storm.component.log;

import cn.bird.storm.component.sensitive.SensitiveFieldLogUtil;
import cn.bird.util.OptionalEmptyUtil;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.util.ArrayUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Objects;
import java.util.Optional;

/**
 * 日志打印切面
 * @author wangxing
 **/
@Aspect
@Slf4j
public class WebLogAspect {
    /** 换行符 */
    protected static final String LINE_SEPARATOR = System.lineSeparator();
    /** 以自定义 @WebLog 注解为切点 */
    @Pointcut("@annotation(cn.bird.storm.component.log.WebLog)")
    public void webLog() {}
    /**
     * 日志打印
     * 返回方法名称
     */
    private String beforeLog(JoinPoint joinPoint) {
        // 开始打印请求日志
        Tuple tuple2 = getCurrentMethod(joinPoint);
        if (Objects.isNull(tuple2)) {
            return "";
        }
        Method currentMethod = tuple2.get(0);
        //只有controller入口处，才打印request信息
        if (AnnotatedElementUtils.hasAnnotation(currentMethod, RequestMapping.class)) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (Objects.nonNull(attributes)) {
                HttpServletRequest request = attributes.getRequest();
                // 打印请求 url
                log.info("[URL]            : {}", request.getRequestURL().toString());
                // 打印 Http method
                log.info("[HTTP Method]    : {}", request.getMethod());
                // 打印请求的 IP
                log.info("[IP]             : {}", request.getRemoteAddr());
            }
        }
        // 获取 @WebLog 注解的描述信息
        String methodDescription = getAspectLogDescription(currentMethod, tuple2.get(1));
        WebLog annotation = currentMethod.getAnnotation(WebLog.class);
        String[] excludeParams = annotation.excludeParams();
        // 打印请求相关参数
        log.info("{}|====================== Start ==========================================", methodDescription);
        // 打印描述信息
        log.info("[Description]    : {}", methodDescription);
        // 打印调用 controller 的全路径以及执行方法
        log.info("[Class Method]   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        // 打印请求入参--入参敏感数据加密或者忽略
        log.info("{}|----- 入参处理----------------start----------------------+" + LINE_SEPARATOR, methodDescription);
        Object[] args = joinPoint.getArgs();
        if (ArrayUtil.isNotEmpty(joinPoint.getArgs())) {
            Parameter[] parameters = currentMethod.getParameters();
            if (args.length == parameters.length) {
                for (int i = 0; i < args.length; i++) {
                    if (ArrayUtil.isEmpty(excludeParams)
                            || !ArrayUtil.contains(excludeParams, parameters[i].getName())) {
                        String lastStr = "";
                        if (i == (args.length - 1)) {
                            lastStr = LINE_SEPARATOR;
                        }
                        log.info("    [参数{}]    ~~~    {} : {}{}", (i + 1), parameters[i].getName(), SensitiveFieldLogUtil.toJacksonJsonStr(args[i]), lastStr);
                    }
                }
            } else {
                log.info("    [Request Args]: {}", SensitiveFieldLogUtil.toJacksonJsonStr(joinPoint.getArgs()));
            }
        }
        log.info("{}|----- 入参处理----------------end----------------------", methodDescription);
        return methodDescription;
    }

    /**
     * 在切点之后织入
     */
    private void afterLog(String methodInfo) {
        // 接口结束后换行，方便分割查看
        log.info("{}|=======================End ===========================================" + LINE_SEPARATOR, methodInfo);
    }

    private Tuple getCurrentMethod(JoinPoint joinPoint){
        try {
            Signature sig = joinPoint.getSignature();
            if (!(sig instanceof MethodSignature)) {
                throw new IllegalArgumentException("该注解只能用于方法");
            }
            MethodSignature msig = (MethodSignature) sig;
            Object target = joinPoint.getTarget();
            Class<?> aClass = target.getClass();
            WebLogClass annotation = aClass.getAnnotation(WebLogClass.class);
            return new Tuple(aClass.getMethod(msig.getName(), msig.getParameterTypes()),
                    Optional.ofNullable(annotation)
                            .map(WebLogClass::value)
                            .orElse("")
            );
        }  catch (NoSuchMethodException e) {
            log.error("获取当前方法出错");
        }
        return null;

    }

    /**
     * 环绕
     * @return 真正的结果
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String methodInfo = beforeLog(proceedingJoinPoint);
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        if (result instanceof Optional && ((Optional<?>) result).isPresent()) {
            // 打印出参--出参敏感数据加密或者忽略
            log.info("{}|--Response Args  : {}", methodInfo, SensitiveFieldLogUtil.toJacksonJsonStr(((Optional<?>) result).get()));
        }else {
            // 打印出参--出参敏感数据加密或者忽略
            log.info("{}|--Response Args  : {}", methodInfo, SensitiveFieldLogUtil.toJacksonJsonStr(result));
        }
        // 执行耗时
        log.info("{}|--Time-Consuming : {} ms", methodInfo, System.currentTimeMillis() - startTime);
        afterLog(methodInfo);
        return result;
    }


    /**
     * 获取切面注解的描述
     *
     * @param method 当前方法
     * @return 描述信息
     */
    public String getAspectLogDescription(Method method, String title) {
        return OptionalEmptyUtil.wrapperOpt(title)
                .map(s -> "『" + title + "』--")
                .orElse("") +
                method.getAnnotation(WebLog.class).value() +
                method.getAnnotation(WebLog.class).description();
    }
}

